#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "kfifo.h"

/**
 * kfifo_init - allocates a new FIFO using a preallocated buffer
 * @buffer: the preallocated buffer to be used.
 * @size: the size of the internal buffer, this have to be a power of 2.
 * @gfp_mask: get_free_pages mask, passed to kmalloc()
 *
 * @return: point to the new FIFO 
 */
struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size)
{
	struct kfifo *fifo;

	/* size must be a power of 2 */
	if (!is_power_of_2(size)) {
		printf("Err: size is not a power of 2, func=%s\n", __FUNCTION__);
		return NULL;
	}

	fifo = malloc(sizeof(struct kfifo));
	if (!fifo) {
		printf("Err: malloc failure, func=%s\n", __FUNCTION__);
		return NULL;
	}

	fifo->buffer = buffer;
	fifo->size = size;
	fifo->in = fifo->out = 0;
	pthread_mutex_init(&fifo->mutex, NULL);

	return fifo;
}

/**
 * kfifo_free - frees the FIFO
 * @fifo: the fifo to be freed.
 */
void kfifo_free(struct kfifo *fifo)
{
	free(fifo->buffer);
	free(fifo);
}

/**
 * __kfifo_put - puts some data into the FIFO, no locking version
 * @fifo: the fifo to be used.
 * @buffer: the data to be added.
 * @len: the length of the data to be added.
 * 
 * If the free space is enough, this function copies @len bytes 
 * from the @buffer into the FIFO depending on the free space, 
 * and returns the number ofbytes copied. 
 * Or return 0.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these functions.
 */
unsigned int __kfifo_put(struct kfifo *fifo,
			 unsigned char *buffer, unsigned int len)
{
	unsigned int n;

	if (len > fifo->size - fifo->in + fifo->out) {
		printf("Err: the free space of the FIFO is not enought.\n");
		return 0;
	}

	/* first put the data starting from fifo->in to buffer end */
	n = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
	memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, n);

	/* then put the rest (if any) at the beginning of the buffer */
	memcpy(fifo->buffer, buffer + n, len - n);

	fifo->in += len;

	return len;
}

/**
 * __kfifo_get - gets some data from the FIFO, no locking version
 * @fifo: the fifo to be used.
 * @buffer: where the data must be copied.
 * @len: the size of the destination buffer.
 *
 * This function copies at most @len bytes from the FIFO into the
 * @buffer and returns the number of copied bytes.
 *
 * Note that with only one concurrent reader and one concurrent
 * writer, you don't need extra locking to use these functions.
 */
unsigned int __kfifo_get(struct kfifo *fifo,
			 unsigned char *buffer, unsigned int len)
{
	unsigned int n;

	len = min(len, fifo->in - fifo->out);

	/* first get the data from fifo->out until the end of the buffer */
	n = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
	memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), n);

	/* then get the rest (if any) from the beginning of the buffer */
	memcpy(buffer + n, fifo->buffer, len - n);

	fifo->out += len;

	return len;
}


unsigned int kfifo_put_nolock(struct kfifo *fifo,
			 unsigned char *buffer, unsigned int len)
{
	return __kfifo_put(fifo, buffer, len);
}

unsigned int kfifo_put(struct kfifo *fifo,
			 unsigned char *buffer, unsigned int len)
{
	unsigned int ret;

	pthread_mutex_lock(&fifo->mutex);
	ret =  __kfifo_put(fifo, buffer, len);
	pthread_mutex_unlock(&fifo->mutex);

	return ret;
}

unsigned int kfifo_get_nolock(struct kfifo *fifo,
			 unsigned char *buffer, unsigned int len)
{
	return __kfifo_get(fifo, buffer, len);
}

unsigned int kfifo_get(struct kfifo *fifo,
			 unsigned char *buffer, unsigned int len)
{
	unsigned int ret;

	pthread_mutex_lock(&fifo->mutex);
	ret = __kfifo_get(fifo, buffer, len);
	pthread_mutex_unlock(&fifo->mutex);

	return ret;
}

/**
 * kfifo_get_in_out: read the index of read and write in the FIFO
 */
unsigned int kfifo_get_in_out(struct kfifo *fifo,
			unsigned int *in, unsigned int *out)
{
	if (fifo == NULL)
		return -1;
	pthread_mutex_lock(&fifo->mutex);
	if (in)
		*in = fifo->in;
	if (out)
		*out = fifo->out;
	pthread_mutex_unlock(&fifo->mutex);

	return 0;
}

/**
 * kfifo_get_data_length: read the length of the rest data in the FIFO
 */
int kfifo_get_rest_data_length(struct kfifo *fifo)
{
	int len;

	if (fifo == NULL)
		return -1;
	pthread_mutex_lock(&fifo->mutex);
	len = fifo->in - fifo->out;
	pthread_mutex_unlock(&fifo->mutex);

	return len;
}